一点点与函数相关的内容。
闭包
闭包( closure )是一个函数,通常也被称为闭包函数或绑定函数,该函数运行在一个特定的环境中,该环境中定义了一些本地变量,当该函数被调用时,仍可以使用这些本地变量。内部函数可以访问外部函数的私有成员。若内部函数引用了外部函数的私有成员,同时内部函数又被传给了外界,或对外界开放,那么闭包就形成了。
特点
- 作为一个函数变量的一个引用,当函数返回时,其处于激活状态
- 一个闭包就是一个函数的返回时,一个没有释放资源的栈区
- 闭包可以在浏览器环境使用,也可以治啊 Node.js 环境使用
创建方式
- 普通函数式
- 立即调用表达式(IIFE)闭包
用途
- 保护单体实例,避免被外部意外修改。实现懒加载,只有在真正需要的时候才会创建实例
- 可以做数据储存,状态持续
- 模块化编程,封装模块的私有变量和方法,防止被外部访问和修改
- 封装私有变量
- 偏函数应用
- 计数器(本质是数据储存)
- 事件处理封装
- 异步编程
不足
- 内存泄漏
- 必报的作用域链,避免意外引用不需要的变量
- 性能问题
- 安全问题
- 可读性问题
内存泄漏
内存泄漏是一个重点。因为闭包会保留对外部作用域的引用,如果闭包一直存在(比如被全局变量引用),外部作用域的变量无法被垃圾回收,可能导致内存泄漏。需要说明如何避免,比如及时释放引用。
function createClosure() {
const largeData = new Array(1000000).fill('data'); // 大数组
return function () {
console.log(largeData.length); // 闭包引用了 largeData
};
}
const closure = createClosure(); // closure 长期存在 → largeData 无法被 GC(垃圾回收)
- 主动释放引用
closure = null; - 规避闭包中保留不必要的变量
循环问题
循环中的闭包问题:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i); // 每次都是 5
}, 1000);
}
利用(IIFE 立即执行函数)闭包原理修改为:
for (var i = 0; i < 5; i++) {
(function (e) {
setTimeout(function () {
console.log(e); // 每次的值都是传入的参数
}, 1000);
})(i);
}
性能问题
闭包的作用域链较长,访问外部变量比访问自身作用域慢,过度使用可能影响性能。需要提醒合理使用,避免不必要的闭包。
this
函数作为对象方法调用时,this 指向对象,但如果作为闭包被赋值给其他变量,this 可能指向全局(严格模式下是 undefined)。需要说明 this 的动态性,闭包中的 this 取决于调用方式。
作用域
闭包是“静态”的作用域:创建时确定,无法动态修改。闭包内部的作用域链是在函数创建时固定,后续外部作用域的变量变化会被闭包感知(因为引用同一变量),但无法通过修改作用域链来访问新的变量。
function outer() {
let x = 1;
const closure = () => x;
x = 2;
return closure;
}
const fn = outer();
console.log(fn()); // 输出 2(闭包访问的是最新的 x)
若要隔离变量,可以显式的创建新的作用域
function outer() {
let x = 1;
const closure = () => {
const x = x; // 复制当前 x 的值(但后续外部 x 变化不影响闭包内的 x)
return x;
};
x = 2;
return closure;
}
const fn = outer();
console.log(fn()); // 输出 1(闭包内的 x 是创建时的副本)
函数柯里化
函数柯里化(Currying)是将一个多参数的原函数转化为一系列单参数(或更少参数)的函数。核心是通过 “分布传参” 的方式,逐步固定部分参数,生成更灵活的子函数。(color pen 就是一个优秀的例子,通过创建各种样式的笔,然后再传入文本进行完成彩色文本的结果展示)。
特性
- 参数 “部分应用”
柯里化的过程本质是部分应用(Partial Application) : 每次传参固定部分参数,生成新的子函数
- 支持 “延迟执行”
柯里化后并不是立即执行原函数,而是等待参数手机完毕。这允许我们动态生成特定配置的函数(而 color pen 这一点是不太相同的, color pen 接受的参数是不固定的,且参数的方式不同)
需注意的项
- 性能开销:柯里化会生成多层闭包和函数调用栈,高频场景(如动画、实时计算)可能影响性能
- 可读性:过度的柯里化导致函数调用链过长,降低代码的可读性
- 参数顺序依赖:柯里化严格参数的顺序,若原函数参数顺序不合理,柯里化后难以使用
- this 绑定:柯里化中的
this需要注意绑定
防抖
防抖的关键是将一件高频事件转化为仅在最后一次触发的单频事件,不关心中间的过程,只关注结果。
- 防抖是一种延迟处理函数的执行技术,确保事件触发后,在指定的时间内没有更多的事件触发时才执行一次的事件处理函数
- 当一个事件触发后,防抖会等待一段时间,如果这段时间没有更多的事件被触发,那么事件处理函数会被执行
适用于
- 防止表单重复提交
- 搜索建议
- 页面滚动到指定位置显示返回按钮
- 页面的 resize 事件
- 实时聊天
function debounce(fn ,delay) {
let timer;
return function(..args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn(...args);
});
}
}
节流
节流的关键是将一件高频的事变成一件低频的事。
- 节流是限制事件处理函数执行频率的技术,确保事件在间隔时间内最多执行一次
- 当一个事件触发,节流会立即执行函数,并确保在时间间隔内忽略后续的相同的事件
适用于
- 鼠标滚轮滚动
- 搜索框输入
- 鼠标移动
- 鼠标的点击
- 轮播图的切换
function throttle(fn, delay, options = {}) {
/** 最后执行的时间毫秒值 */
let lastExecutionTime = 0;
return function (...args) {
const currentTime = Date.now();
if (currentTime - lastExecutionTime >= delay) {
fn(...args);
lastExecutionTime = currentTime;
}
};
}